/**
 * ***************************************************************************** Copyright (c) 2006,
 * 2008 IBM Corporation and others. All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0 which accompanies this
 * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * <p>Contributors: IBM Corporation - initial API and implementation
 * *****************************************************************************
 */
package org.eclipse.ltk.internal.core.refactoring.resource.undostates;

import java.net.URI;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;

/**
 * ContainerUndoState is a lightweight description that describes a container to be created.
 *
 * <p>This class is not intended to be instantiated or used by clients.
 *
 * @since 3.4
 */
public abstract class ContainerUndoState extends AbstractResourceUndoState {

  protected String name;
  protected URI location;

  private String defaultCharSet;
  private AbstractResourceUndoState[] members;

  /**
   * Create a container description from the specified container handle that can be used to create
   * the container. The returned ContainerState should represent any non-existing parents in
   * addition to the specified container.
   *
   * @param container the handle of the container to be described
   * @return a container description describing the container and any non-existing parents.
   */
  public static ContainerUndoState fromContainer(IContainer container) {
    IPath fullPath = container.getFullPath();
    ContainerUndoState firstCreatedParent = null;
    ContainerUndoState currentContainerDescription = null;

    // Does the container exist already? If so, then the parent exists and
    // we use the normal creation constructor.
    IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
    IContainer currentContainer = (IContainer) root.findMember(fullPath);
    if (currentContainer != null) {
      return (ContainerUndoState) ResourceUndoState.fromResource(container);
    }

    // Create container descriptions for any uncreated parents in the given
    // path.
    currentContainer = root;
    for (int i = 0; i < fullPath.segmentCount(); i++) {
      String currentSegment = fullPath.segment(i);
      IResource resource = currentContainer.findMember(currentSegment);
      if (resource != null) {
        // parent already exists, no need to create a description for it
        currentContainer = (IContainer) resource;
      } else {
        if (i == 0) {
          // parent does not exist and it is a project
          firstCreatedParent = new ProjectUndoState(root.getProject(currentSegment));
          currentContainerDescription = firstCreatedParent;
        } else {
          IFolder folderHandle = currentContainer.getFolder(new Path(currentSegment));
          ContainerUndoState currentFolder = new FolderUndoState(folderHandle);
          currentContainer = folderHandle;
          if (currentContainerDescription != null) {
            currentContainerDescription.addMember(currentFolder);
          }
          currentContainerDescription = currentFolder;
          if (firstCreatedParent == null) {
            firstCreatedParent = currentFolder;
          }
        }
      }
    }
    return firstCreatedParent;
  }

  /** Create a ContainerState with no state. */
  public ContainerUndoState() {}

  /**
   * Create a ContainerState from the specified container handle. Typically used when the container
   * handle represents a resource that actually exists, although it will not fail if the resource is
   * non-existent.
   *
   * @param container the container to be described
   */
  public ContainerUndoState(IContainer container) {
    super(container);
    this.name = container.getName();
    if (container.isLinked()) {
      this.location = container.getLocationURI();
    }
    try {
      if (container.isAccessible()) {
        defaultCharSet = container.getDefaultCharset(false);
        IResource[] resourceMembers = container.members();
        members = new AbstractResourceUndoState[resourceMembers.length];
        for (int i = 0; i < resourceMembers.length; i++) {
          members[i] =
              (AbstractResourceUndoState) ResourceUndoState.fromResource(resourceMembers[i]);
        }
      }
    } catch (CoreException e) {
      // Eat this exception because it only occurs when the resource
      // does not exist and we have already checked this.
      // We do not want to throw exceptions on the simple constructor, as
      // no one has actually tried to do anything yet.
    }
  }

  /**
   * Create any child resources known by this container description.
   *
   * @param parentHandle the handle of the created parent
   * @param monitor the progress monitor to be used
   * @param ticks the number of ticks allocated for creating children
   * @throws CoreException if creation failed
   */
  protected void createChildResources(IContainer parentHandle, IProgressMonitor monitor, int ticks)
      throws CoreException {

    // restore any children
    if (members != null && members.length > 0) {
      for (int i = 0; i < members.length; i++) {
        members[i].parent = parentHandle;
        members[i].createResource(new SubProgressMonitor(monitor, ticks / members.length));
      }
    }
  }

  public void recordStateFromHistory(IResource resource, IProgressMonitor monitor)
      throws CoreException {
    monitor.beginTask(RefactoringCoreMessages.FolderDescription_SavingUndoInfoProgress, 100);
    if (members != null) {
      for (int i = 0; i < members.length; i++) {
        if (members[i] instanceof FileUndoState) {
          IPath path = resource.getFullPath().append(((FileUndoState) members[i]).name);
          IFile fileHandle = resource.getWorkspace().getRoot().getFile(path);
          members[i].recordStateFromHistory(
              fileHandle, new SubProgressMonitor(monitor, 100 / members.length));
        } else if (members[i] instanceof FolderUndoState) {
          IPath path = resource.getFullPath().append(((FolderUndoState) members[i]).name);
          IFolder folderHandle = resource.getWorkspace().getRoot().getFolder(path);
          members[i].recordStateFromHistory(
              folderHandle, new SubProgressMonitor(monitor, 100 / members.length));
        }
      }
    }
    monitor.done();
  }

  /**
   * Return the name of the container described by this ContainerState.
   *
   * @return the name of the container.
   */
  public String getName() {
    return name;
  }

  /**
   * Return the first folder found that has no child folders.
   *
   * @return the container description for the first child in the receiver that is a leaf, or this
   *     container if there are no children.
   */
  public ContainerUndoState getFirstLeafFolder() {
    // If there are no members, this is a leaf
    if (members == null || members.length == 0) {
      return this;
    }
    // Traverse the members and find the first potential leaf
    for (int i = 0; i < members.length; i++) {
      if (members[i] instanceof ContainerUndoState) {
        return ((ContainerUndoState) members[i]).getFirstLeafFolder();
      }
    }
    // No child folders were found, this is a leaf
    return this;
  }

  /**
   * Add the specified resource description as a member of this resource description
   *
   * @param member the resource description considered a member of this container.
   */
  public void addMember(AbstractResourceUndoState member) {
    if (members == null) {
      members = new AbstractResourceUndoState[] {member};
    } else {
      AbstractResourceUndoState[] expandedMembers =
          new AbstractResourceUndoState[members.length + 1];
      System.arraycopy(members, 0, expandedMembers, 0, members.length);
      expandedMembers[members.length] = member;
      members = expandedMembers;
    }
  }

  protected void restoreResourceAttributes(IResource resource) throws CoreException {
    super.restoreResourceAttributes(resource);
    Assert.isLegal(resource instanceof IContainer);
    IContainer container = (IContainer) resource;
    if (defaultCharSet != null) {
      container.setDefaultCharset(defaultCharSet, null);
    }
  }

  /**
   * Set the location to which this container is linked.
   *
   * @param location the location URI, or <code>null</code> if there is no link
   */
  public void setLocation(URI location) {
    this.location = location;
  }

  public boolean verifyExistence(boolean checkMembers) {
    boolean existence = super.verifyExistence(checkMembers);
    if (existence) {
      if (checkMembers) {
        // restore any children
        if (members != null && members.length > 0) {
          for (int i = 0; i < members.length; i++) {
            if (!members[i].verifyExistence(checkMembers)) {
              return false;
            }
          }
        }
      }
      return true;
    }
    return false;
  }
}
